package cn.dansj.common.request.resolver;

import cn.dansj.common.request.annotation.http.RequestParam;
import cn.dansj.common.utils.crypt.Crypt;
import cn.dansj.common.utils.enums.BooleanType;
import cn.dansj.common.utils.json.JSONUtils;
import cn.dansj.common.utils.json.JSONObject;
import cn.dansj.common.utils.reflect.ClassUtils;
import cn.dansj.common.utils.spring.request.RequestUtils;
import cn.dansj.common.utils.spring.context.ApplicationContextProvider;
import cn.dansj.common.utils.transfer.*;

import javax.servlet.ServletException;

import org.springframework.beans.factory.BeanCreationException;
import org.springframework.core.MethodParameter;
import org.springframework.lang.NonNull;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ValueConstants;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerMapping;

import java.util.*;

public class RequestParamResolver extends AbstractNamedValueMethodArgumentResolver {
    private final Crypt cryptBean = ApplicationContextProvider.hasBean(Crypt.class) ? ApplicationContextProvider.getBean(Crypt.class) : DefaultCrypt.instance;
    private final List<Class<?>> packageClass = ArrayUtils.asList(Integer.class, Long.class, Byte.class, Boolean.class, Short.class, Character.class, Float.class, Double.class);
    private final List<Class<?>> booleanClass = ArrayUtils.asList(Boolean.class, boolean.class);

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestParam.class);
    }

    @NonNull
    @Override
    protected RequestParamNamedValueInfo createNamedValueInfo(MethodParameter parameter) {
        RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
        return (requestParam != null ? new RequestParamNamedValueInfo(requestParam) : new RequestParamNamedValueInfo());
    }

    @Override
    protected Object resolveName(@NonNull String paramName, MethodParameter parameter, @NonNull NativeWebRequest webRequest) throws Exception {
        RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
        assert requestParam != null;
        JSONObject requestParams = RequestUtils.getRequestParams();
        if (requestParam.path()) {
            requestParams.putAll(JSONUtils.from(webRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST)).toJSONObject());
        }

        String name = requestParam.name();
        String nvlName = Transformation.nvl(name, paramName);
        boolean iteration = requestParam.iteration();
        final Class<? extends Crypt> encryptAndDecryptClass = requestParam.cryptClass();
        AssertUtils.state(!ClassUtils.isAbstract(encryptAndDecryptClass), () -> new IllegalArgumentException("Crypt不能是个抽象类"));
        Crypt crypt = ClassUtils.isInterface(encryptAndDecryptClass) ? cryptBean : encryptAndDecryptClass.getDeclaredConstructor().newInstance();
        AssertUtils.notNull(crypt, () -> new BeanCreationException("默认的Crypt未配置"));

        final Class<?> parameterType = parameter.getParameterType();

        if (iteration) {
            return iteration(requestParams, nvlName, parameterType, requestParam, crypt);
        }
        return getValue(requestParams, nvlName, parameterType, requestParam, Verification.checkNull(name) && !requestParams.containsKey(nvlName), crypt);
    }

    /**
     * 迭代生成
     */
    private Object iteration(JSONObject requestParams, String name, Class<?> parameterType, RequestParam requestParam, Crypt crypt) throws ServletRequestBindingException {
        List<String> list = ArrayUtils.asList(StringUtils.split(name, "."));
        List<String> iterKey = list.size() > 1 ? list.subList(0, list.size() - 1) : new ArrayList<>();
        String finalKey = list.get(list.size() - 1);
        if (requestParams == null) {
            return null;
        }

        JSONObject temp = JSONUtils.from(requestParams).toJSONObject();
        for (String key : iterKey) {
            if (temp.isEmpty() && requestParam.required()) {
                throw new ServletRequestBindingException("~ Required request parameter '" + name + "' for method parameter type " + parameterType.getSimpleName() + " is not present");
            }
            temp = temp.getJSONObject(key);
        }
        if (requestParam.required() && !temp.containsKey(finalKey)) {
            throw new ServletRequestBindingException("~~ Required request parameter '" + name + "' for method parameter type " + parameterType.getSimpleName() + " is not present");
        }
        return getValue(temp, finalKey, parameterType, requestParam, false, crypt);
    }

    private boolean checkParameterType(Class<?> parameterType) {
        return parameterType.isPrimitive() || packageClass.contains(parameterType);
    }

    /**
     * 根据返回值类型生成返回数据
     */
    private Object getValue(JSONObject requestParams, String name, Class<?> parameterType, RequestParam requestParam, boolean allReturn, Crypt crypt) throws ServletRequestBindingException {
        if (requestParam.required() && !requestParams.containsKey(name) && (!allReturn || checkParameterType(parameterType) || (parameterType.equals(String.class) && !requestParam.allGet().equals(BooleanType.TRUE)))) {
            throw new ServletRequestBindingException("~~~ Required request parameter '" + name + "' for method parameter type " + parameterType.getSimpleName() + " is not present");
        }
        /* 参数类型为String时,直接获取name,判断是否加解密处理后返回数据 */
        if (parameterType.equals(String.class)) {
            String tempValue = BooleanType.TRUE.equals(requestParam.allGet()) ? RequestUtils.getRequestParam() : requestParams.getString(name);
            final boolean decrypt = requestParam.decrypt();
            if (decrypt) {
                return crypt.decrypt(tempValue);
            } else {
                return tempValue;
            }
        }

        /* 参数类型为JSONObject时
        if (parameterType.equals(JSONObject.class)) {
            if ((allReturn && !BooleanType.FALSE.equals(requestParam.allGet())) || BooleanType.TRUE.equals(requestParam.allGet())) {
                return requestParams;
            }
            return requestParams.getJSONObject(name);
        } */

        /* 参数类型为Map时 */
        if (Map.class.isAssignableFrom(parameterType)) {
            if ((allReturn && !BooleanType.FALSE.equals(requestParam.allGet())) || BooleanType.TRUE.equals(requestParam.allGet())) {
                return requestParams;
            }
            return requestParams.getJSONObject(name);
        }

        /* 参数类型为列表时 */
        if (Collection.class.isAssignableFrom(parameterType)) {
            if ((allReturn && !BooleanType.FALSE.equals(requestParam.allGet())) || BooleanType.TRUE.equals(requestParam.allGet())) {
                return requestParams.keySet().stream().map(e -> new HashMap<String, Object>() {
                    {
                        put(e, requestParams.get(e));
                    }
                });
            }
            return requestParams.get(name) != null ? StringUtils.split(requestParams.get(name).toString(), ",") : null;
        }

        try {
            if (checkParameterType(parameterType)) {
                Object V = requestParams.get(name);
                if (Verification.checkNull(V) && requestParams.containsKey(name)) {
                    if (booleanClass.contains(parameterType)) {
                        return true;
                    } else {
                        return null;
                    }
                }
                return V;
            } else {
                if ((allReturn && !BooleanType.FALSE.equals(requestParam.allGet())) || BooleanType.TRUE.equals(requestParam.allGet())) {
                    return JSONUtils.toObj(requestParams, parameterType);
                }
                return JSONUtils.toObj(requestParams.get(name), parameterType);
            }
        } catch (Exception e) {
            if ((allReturn && !BooleanType.FALSE.equals(requestParam.allGet())) || BooleanType.TRUE.equals(requestParam.allGet())) {
                return requestParams;
            } else {
                return requestParams.get(name);
            }
        }
    }

    @Override
    protected void handleMissingValue(@NonNull String name, @NonNull MethodParameter parameter, @NonNull NativeWebRequest request) throws Exception {
        super.handleMissingValue(name, parameter, request);
    }

    @Override
    protected void handleMissingValue(@NonNull String name, @NonNull MethodParameter parameter) throws ServletException {
        super.handleMissingValue(name, parameter);
    }

    @Override
    protected void handleMissingValueAfterConversion(@NonNull String name, @NonNull MethodParameter parameter, @NonNull NativeWebRequest request) throws Exception {
        super.handleMissingValueAfterConversion(name, parameter, request);
    }

    @Override
    protected void handleResolvedValue(Object arg, @NonNull String name, @NonNull MethodParameter parameter, ModelAndViewContainer mavContainer, @NonNull NativeWebRequest webRequest) {
        super.handleResolvedValue(arg, name, parameter, mavContainer, webRequest);
    }

    public static class RequestParamNamedValueInfo extends NamedValueInfo {
        public RequestParamNamedValueInfo() {
            super("", false, ValueConstants.DEFAULT_NONE);
        }

        public RequestParamNamedValueInfo(RequestParam annotation) {
            super(annotation.name(), annotation.required(), annotation.defaultValue());
        }
    }

    static class DefaultCrypt implements Crypt {
        private static final Crypt instance = new DefaultCrypt();

        @Override
        public String decrypt(String data) {
            return data;
        }
    }
}
